package cn.bearspark.framework.redis.starter.buffer.hash.bi;

import cn.bearspark.framework.redis.starter.buffer.hash.RedisBaseHashBuffer;
import cn.hutool.core.bean.BeanUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static java.util.stream.Collectors.*;

/**
 * Redis 的 hash 数据结构的缓存器的抽象父类
 * <p>
 * 注：本缓存器用于主键 id 为 long 类型字段 + int 类型字段 的数据
 *
 * @param <CO> 缓存对象的类型
 * @author f
 */
public abstract class RedisBiHashBuffer<CO extends BiCacheable> extends RedisBaseHashBuffer<CO> {

    /**
     * 从数据库中查不到的 co 对象的生成器，需要设置 id 和 热数据，防止 RedisBuffer 查询数据库
     */
    private final Function<Map<Long, Set<Integer>>, Map<Long, Map<Integer, CO>>> deletedCOGenerator;

    private final RedisBiHashMapper<CO> mapper;

    private final RedisBiHashBufferKeyBuilder keyBuilder;

    public RedisBiHashBuffer(RedisTemplate<String, Object> redisTemplate,
                             int cacheExpire,
                             Class<CO> coClazz,
                             RedisBiHashMapper<CO> mapper,
                             RedisBiHashBufferKeyBuilder keyBuilder) {
        this(
                redisTemplate, cacheExpire, coClazz,
                mapper, keyBuilder,
                deletedCOIdList -> Map.of()
        );
    }

    public RedisBiHashBuffer(RedisTemplate<String, Object> redisTemplate,
                             int cacheExpire,
                             Class<CO> coClazz,
                             RedisBiHashMapper<CO> mapper,
                             RedisBiHashBufferKeyBuilder keyBuilder,
                             Function<Map<Long, Set<Integer>>, Map<Long, Map<Integer, CO>>> deletedCOGenerator) {
        super(redisTemplate, cacheExpire, coClazz);

        this.mapper = mapper;
        this.keyBuilder = keyBuilder;
        this.deletedCOGenerator = deletedCOGenerator;
    }

    /**
     * 批量缓存数据
     *
     * @param coMap 待缓存的数据映射
     */
    public void cacheBatch(Map<Long, Map<Integer, CO>> coMap) {
        if (CollectionUtils.isEmpty(coMap)) {
            return;
        }

        super.executePipelined(operations -> coMap.forEach((id1, innerCOMap) -> innerCOMap.forEach((id2, co) -> {
            String bufferKey = keyBuilder.buildBiHashBufferKey(id1, id2);
            operations.opsForHash().putAll(bufferKey, BeanUtil.beanToMap(co));
            operations.expire(bufferKey, cacheExpire, TimeUnit.SECONDS);
        })));
    }

    /**
     * 获取缓存的数据
     *
     * @param id1 待获取数据的 long 类型的 id
     * @param id2 待获取数据的 integer 类型的 id
     * @return 缓存的数据
     */
    public CO get(long id1, int id2) {
        CO co = fromEntries(redisTemplate.opsForHash().entries(keyBuilder.buildBiHashBufferKey(id1, id2)));
        try {
            // 如果缓存对象的 id 为 null，则这个缓存对象是缺失的
            if (co.id1() == null || co.id2() == null) {
                return caching(Map.of(id1, Set.of(id2))).get(id1).get(id2);
            }

            // 如果第一个热数据字段的值为 null，则这个缓存对象是不完整的
            if (hotFieldGetters[0].invoke(co) == null) {
                replenish(List.of(co));
            }

            return co;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取缓存的数据
     *
     * @param idMap 待获取数据的缓存的 id 映射
     * @return 缓存数据的嵌套映射
     */
    public Map<Long, Map<Integer, CO>> map(Map<Long, Set<Integer>> idMap) {
        List<CO> incompleteCOList = new ArrayList<>();
        Map<Long, Set<Integer>> missingCOIdMap = new HashMap<>();

        List<CO> coList = new ArrayList<>();
        super.execute(operations -> idMap.forEach((id1, id2Set) -> id2Set.forEach(id2 -> {
            CO co = fromEntries(operations.opsForHash().entries(keyBuilder.buildBiHashBufferKey(id1, id2)));
            checkCOAndRecord(incompleteCOList, missingCOIdMap, coList, id1, id2, co);
        })));

        return getLongIntegerCOMap(coList, missingCOIdMap, incompleteCOList);
    }

    /**
     * 获取缓存的部分数据
     *
     * @param idMap         待获取数据的缓存的 id 映射
     * @param entryNameList 指定对象的部分字段的名称集合
     * @return 缓存的部分数据映射
     */
    protected Map<Long, Map<Integer, CO>> mapLess(Map<Long, Set<Integer>> idMap, List<String> entryNameList) {
        List<CO> incompleteCOList = new ArrayList<>();
        Map<Long, Set<Integer>> missingCOIdMap = new HashMap<>();

        List<CO> coList = new ArrayList<>();
        super.execute(operations -> idMap.forEach((id1, id2Set) -> id2Set.forEach(id2 -> {
            CO co = fromEntries(getEntries(keyBuilder.buildBiHashBufferKey(id1, id2),
                    entryNameList, operations.opsForHash()
            ));
            checkCOAndRecord(incompleteCOList, missingCOIdMap, coList, id1, id2, co);
        })));

        return getLongIntegerCOMap(coList, missingCOIdMap, incompleteCOList);
    }

    // 检查传入的 co 有没有 缺失热数据、缺失 的问题，如果有，则加入相应的集合中
    private void checkCOAndRecord(List<CO> incompleteCOList, Map<Long, Set<Integer>> missingCOIdMap,
                                  List<CO> coList, Long id1, Integer id2, CO co) {
        try {
            if (co.id1() == null || co.id2() == null) {
                // 如果缓存对象的 id 为 null，则这个缓存对象是缺失的
                missingCOIdMap.computeIfAbsent(id1, k -> new HashSet<>()).add(id2);
            } else if (hotFieldGetters[0].invoke(co) == null) {
                // 如果第一个热数据字段的值为 null，则这个缓存对象是不完整的
                incompleteCOList.add(co);
                coList.add(co);
            } else {
                coList.add(co);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 将原本的 缓存 list 集合 转换成 缓存嵌套映射，顺便补充缺少的(热)数据
    private Map<Long, Map<Integer, CO>> getLongIntegerCOMap(List<CO> coList,
                                                            Map<Long, Set<Integer>> missingCOIdMap,
                                                            List<CO> incompleteCOList) {
        Map<Long, Map<Integer, CO>> coMap = toNestedMap(coList);

        // 补充缺少的数据
        if (!missingCOIdMap.isEmpty()) {
            coMap.putAll(caching(missingCOIdMap));
        }

        // 补充缺少的热数据
        incompleteCOList = incompleteCOList.stream().filter(co -> co.id1() != null && co.id2() != null).toList();
        if (!incompleteCOList.isEmpty()) {
            replenish(incompleteCOList);
        }

        return coMap;
    }

    // 将 List<CO> 转换成 Map<Long, Map<Integer, CO>> 的嵌套映射
    private Map<Long, Map<Integer, CO>> toNestedMap(List<CO> coList) {
        return coList.stream().collect(groupingBy(
                BiCacheable::id1, toMap(BiCacheable::id2, Function.identity())
        ));
    }

    // 查询没有找到的 co 对象，并将其缓存起来
    private Map<Long, Map<Integer, CO>> caching(Map<Long, Set<Integer>> missingCOIdMap) {
        // 查询没有找到的 co 对象
        Map<Long, Map<Integer, CO>> missingCOMap = toNestedMap(mapper.listData(
                missingCOIdMap.entrySet().stream().map(BiQO::new).collect(toSet())
        ));

        Map<Long, Set<Integer>> deletedCOIdMap = new HashMap<>();
        missingCOIdMap.forEach((id1, id2Set) -> {
            Map<Integer, CO> innerCOMap = missingCOMap.get(id1);
            if (innerCOMap == null) {
                deletedCOIdMap.put(id1, id2Set);
                return;
            }
            id2Set.forEach(id2 -> {
                if (!innerCOMap.containsKey(id2)) {
                    deletedCOIdMap.computeIfAbsent(id1, k -> new HashSet<>()).add(id2);
                }
            });
        });

        // 如果在数据库中找不到 co 对象，则说明 co 对象被删除，这时根据 子缓存器生成新对象的策略 生成新的对象，并暂时缓存起来
        if (!deletedCOIdMap.isEmpty()) {
            missingCOMap.putAll(deletedCOGenerator.apply(deletedCOIdMap));
        }

        // 缓存没有找到的 co 对象
        cacheBatch(missingCOMap);
        return missingCOMap;
    }

    // 补充缓存对象缺少的热数据
    private void replenish(List<CO> incompleteCOList) {
        Map<Long, Map<Integer, CO>> incompleteCOMap = toNestedMap(incompleteCOList);
        // 补充热数据
        mapper.listHotData(incompleteCOMap.entrySet().stream().map(entry -> {
            BiQO biQO = new BiQO();
            biQO.setId1(entry.getKey());
            biQO.setId2Set(entry.getValue().keySet());
            return biQO;
        }).collect(toSet())).forEach(hotData -> {
            try {
                CO incompleteCO = incompleteCOMap.get(hotData.id1()).get(hotData.id2());
                for (int i = 0; i < hotFieldSetters.length; i++) {
                    Method hotFieldSetter = hotFieldSetters[i];
                    Method hotFieldGetter = hotFieldGetters[i];
                    hotFieldSetter.invoke(incompleteCO, hotFieldGetter.invoke(hotData));
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        // 缓存整个缓存对象
        cacheBatch(incompleteCOMap);
    }

    /**
     * 清除缓存中的热数据
     *
     * @param id1 待获取数据的 long 类型的 id
     * @param id2 待获取数据的 integer 类型的 id
     */
    public void cleanHot(long id1, int id2) {
        redisTemplate.opsForHash().delete(keyBuilder.buildBiHashBufferKey(id1, id2), hotFieldsName);
    }

    /**
     * 清除缓存数据
     *
     * @param id1 待获取数据的 long 类型的 id
     * @param id2 待获取数据的 integer 类型的 id
     */
    public void clean(long id1, int id2) {
        redisTemplate.delete(keyBuilder.buildBiHashBufferKey(id1, id2));
    }

    /**
     * 批量清除缓存中的热数据
     *
     * @param id1     待清除数据的 long 类型的 id
     * @param id2List 待清除数据的 integer 类型的 id 集合
     */
    public void cleanBatchHot(long id1, List<Integer> id2List) {
        super.executePipelined(operations -> id2List.forEach(id2 ->
                operations.opsForHash().delete(keyBuilder.buildBiHashBufferKey(id1, id2), hotFieldsName)
        ));
    }

    /**
     * 批量清除缓存中的数据
     *
     * @param id1 待清除数据的 long 类型的 id
     */
    public void cleanBatch(long id1) {
        super.cleanWithPattern(keyBuilder.buildBiHashBufferKeyPattern(id1));
    }
}
